package com.wz.dhs.exception;


import com.dtflys.forest.exceptions.ForestNetworkException;
import com.wz.dhs.model.common.Result;
import com.wz.dhs.model.common.ResultCode;
import com.wz.dhs.utils.MessageUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * 全局异常处理<br>
 * 注:{@link } 处理error日志 所有抛出ServiceException的日志都将打印error级别的日志
 * 若需打印warn日志请使用{@link BusinessException}
 *
 * @author biyxkj7
 */

@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    private final static String RESPONSE_DATA = "responseData";

    /**
     * 违反约束，javax扩展定义
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Result resolveConstraintViolationException(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        StringBuilder strBuilder = new StringBuilder();
        for (ConstraintViolation<?> violation : violations) {
            strBuilder.append(violation.getInvalidValue()).append(" ").append(violation.getMessage()).append("\n");
        }
        String errorMessage = strBuilder.toString();
        logger.warn("参数错误:", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (errorMessage != null) {
            Result result = Result.fail(ResultCode.PARAMS_IS_INVALID.getCode(), errorMessage);
            request.setAttribute(RESPONSE_DATA, result);
            return result;
        }
        Result result = Result.fail(ResultCode.PARAMS_IS_INVALID.getCode(), ex.getMessage());
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }

    /**
     * 参数无效，如JSON请求参数违反约束
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        //获取错误信息
        String errorMessage = buildMessages(ex.getBindingResult());
        logger.debug("参数无效:", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (errorMessage != null) {
            Result result = Result.fail(ResultCode.PARAMS_IS_INVALID.getCode(), errorMessage);
            request.setAttribute(RESPONSE_DATA, result);
            return result;
        }
        Result result = Result.fail(ResultCode.PARAMS_IS_INVALID.getCode(), ex.getMessage());
        request.setAttribute(RESPONSE_DATA, result);
        return result;

    }

    /**
     * 绑定失败，如表单对象参数违反约束
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(BindException.class)
    public Result resolveBindException(BindException ex) {
        //获取错误信息
        String errorMessage = buildMessages(ex.getBindingResult());
        logger.debug("参数绑定错误:", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (errorMessage != null) {
            Result result = Result.fail(ResultCode.PARAMS_IS_INVALID.getCode(), errorMessage);
            request.setAttribute(RESPONSE_DATA, result);
            return result;
        }
        Result result = Result.fail(ResultCode.PARAMS_IS_INVALID.getCode(), ex.getMessage());
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }

    /**
     * 参数缺失
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result resolveMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
        logger.debug("参数缺失:", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Result result = Result.fail(ResultCode.PARAMS_NOT_COMPLETE.getCode(), ResultCode.PARAMS_NOT_COMPLETE.getMsg() + ":" + ex.getMessage());
        request.setAttribute(RESPONSE_DATA, result);
        return result;

    }

    /**
     * 参数类型不匹配
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(TypeMismatchException.class)
    public Result resolveTypeMismatchException(TypeMismatchException ex) {
        logger.debug("参数类型不匹配:", ex);
        //TODO 配合链路日志
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Result result = Result.fail(ResultCode.PARAMS_TYPE_ERROR.getCode(), ResultCode.PARAMS_TYPE_ERROR.getMsg() + ":" + ex.getMessage());
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }


    /**
     * 业务异常处理
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    public Result resolveBusinessException(BusinessException ex) {
        logger.warn("业务异常", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Result result = Result.fail(ex.getCode(), ex.getMessage());
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }

    /**
     * 权限不足处理
     */
    @ExceptionHandler(AccessDeniedException.class)
    public Result resolveAccessDeniedException(AccessDeniedException ex) {
        logger.warn("权限异常", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Result result = Result.fail(ResultCode.AUTH_THROITY_FAIL);
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }

    /**
     * Rpc异常处理
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(RpcException.class)
    public Result resolveRpcException(RpcException ex) {
        logger.error("远程调用异常:{}", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Result result = Result.fail(ex.getCode(), ex.getMessage());
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }

    /**
     * 请求方式异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result resolveMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) {
        logger.warn("请求方式异常:", ex);
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        Result result = Result.fail(ResultCode.HTTP_REQUEST_METHOD_ERROR);
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }


    /**
     * 系统内部错误
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result resolveException(Exception ex) {
        logger.error("系统内部错误:", ex);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Result result = Result.fail(MessageUtils.get(ResultCode.ERROR.getCode()));
        request.setAttribute(RESPONSE_DATA, result);
        return result;
    }

    @ExceptionHandler(ForestNetworkException.class)
    public Result resolveForestNetworkException(ForestNetworkException ex) {
        logger.error("http接口调用异常:", ex);
        Result result = Result.fail(ex.getResponse().getContent());
        return result;
    }

    /**
     * 构建错误信息
     *
     * @param result
     * @return
     */
    private String buildMessages(BindingResult result) {
        StringBuilder resultBuilder = new StringBuilder();

        List<ObjectError> errors = result.getAllErrors();
        if (errors != null && errors.size() > 0) {
            for (ObjectError error : errors) {
                if (error instanceof FieldError) {
                    FieldError fieldError = (FieldError) error;
                    String fieldErrMsg = fieldError.getDefaultMessage();
                    return resultBuilder.append(fieldErrMsg).toString();
                }
            }
        }
        return resultBuilder.toString();
    }
}
